package com.hilihili.transcode;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
import javax.crypto.KeyGenerator;

import com.hilihili.config.TranscodeConfig;
import com.hilihili.domain.vo.MediaInfo;
import com.hilihili.util.BeanUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Hex;


import com.google.gson.Gson;


/**
 * ffmpegUtils 视频文件转 u3m8格式
 *
 * @author machi
 * @date 2023/12/16
 */
@Slf4j
public class FFmpegUtils {
    // 跨平台换行符
    private static final String LINE_SEPARATOR = System.getProperty("line.separator");
    public static final TranscodeConfig transcodeConfig = BeanUtil.getBean(TranscodeConfig.class);
    private String ffmpeg;
    private String ffprobe;

    public FFmpegUtils(String fileType) {
        if ("large".equals(fileType)){
            this.ffmpeg = transcodeConfig.getLargeFfmpeg();
            this.ffprobe = transcodeConfig.getLargeFfprobe();
        }else {
            this.ffmpeg = transcodeConfig.getSmallFfmpeg();
            this.ffprobe = transcodeConfig.getSmallFfprobe();
        }
    }

    /**
     * 在指定的目录下生成key_info, key文件，返回key_info文件
     *
     * @param folder
     * @throws IOException
     */
    private Path genKeyInfo(String folder) throws IOException {
        // AES 密钥
        byte[] aesKey = genAesKey();
        // AES 向量
        String iv = Hex.encodeHexString(genAesKey());
        // key 文件写入 分别在分辨率文件夹下 写入密钥
        String[] resolutions = {"1080p", "720p", "480p", "360p"};
        StringBuilder stringBuilder = null;
        for (String resolution : resolutions) {
            // 创建工作目录
            Path workDir = Paths.get(folder, resolution);
            Files.createDirectories(workDir);
            Path keyFile = Paths.get(workDir.toString(), "key");
            Files.write(keyFile, aesKey, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);

            // key_info 文件写入
            stringBuilder = new StringBuilder();
            // m3u8加载key文件网络路径
            stringBuilder.append("key").append(LINE_SEPARATOR);
            // FFmeg加载key_info文件路径
            stringBuilder.append(keyFile.toString()).append(LINE_SEPARATOR);
            // ASE 向量
            stringBuilder.append(iv);

        }
        Path keyInfo = Paths.get(folder, "key_info");
        Files.write(keyInfo, stringBuilder.toString().getBytes(), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
        return keyInfo;
    }

    /**
     * 生成随机16个字节的AESKEY
     *
     * @return
     */
    private byte[] genAesKey() {
        try {
            KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
            keyGenerator.init(128);
            return keyGenerator.generateKey().getEncoded();
        } catch (NoSuchAlgorithmException e) {
            return null;
        }
    }

    public void transcodeToM3u8(String source, String destFolder, AbstractTranscodeCommand command) throws IOException, InterruptedException {
        // 判断源视频是否存在
        if (!Files.exists(Paths.get(source))) {
            log.error("文件不存在：" + source);
        }

        // 在工作目录生成KeyInfo文件
        Path keyInfo = genKeyInfo(destFolder);
        // 构建进程
        command.setSource(source);
        command.setDestFolder(destFolder);
        command.setKeyInfo(keyInfo.toString());
        List<String> transcodeCommand = command.getTranscodeCommand(ffmpeg);
        System.out.println("command.toString() = " + command);
        Process process = new ProcessBuilder()
                .command(transcodeCommand)
                .directory(Paths.get(destFolder).toFile())
                .start();
        // 读取进程标准输出
//        new Thread(() -> {
//            try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
//                String line = null;
//                while ((line = bufferedReader.readLine()) != null) {
//                    log.info(line);
//                }
//            } catch (IOException e) {
//            }
//        }).start();

        // 读取进程异常输出
        new Thread(() -> {
            try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getErrorStream()))) {
                String line = null;
                while ((line = bufferedReader.readLine()) != null) {
                    log.info(line);
                }
            } catch (IOException e) {
            }
        }).start();


        // 阻塞直到任务结束
        if (process.waitFor() != 0) {
            throw new RuntimeException("视频切片异常");
        }

        // 获取视频信息
        MediaInfo mediaInfo = getMediaInfo(source);
        if (mediaInfo == null) {
            throw new RuntimeException("获取媒体信息异常");
        }

        Files.delete(keyInfo);
        long end = System.currentTimeMillis();

    }

    /**
     * 获取视频文件的媒体信息
     *
     * @param source
     * @return
     * @throws IOException
     * @throws InterruptedException
     */
    public MediaInfo getMediaInfo(String source) throws IOException, InterruptedException {
        List<String> commands = new ArrayList<>();
        commands.add(ffprobe);
        commands.add("-i");
        commands.add(source);
        commands.add("-show_format");
        commands.add("-show_streams");
        commands.add("-print_format");
        commands.add("json");
        Process process = new ProcessBuilder(commands)
                .start();

        MediaInfo mediaInfo = null;

        try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
            mediaInfo = new Gson().fromJson(bufferedReader, MediaInfo.class);
        } catch (IOException e) {
            e.printStackTrace();
        }

        if (process.waitFor() != 0) {
            return null;
        }

        return mediaInfo;
    }

    /**
     * 截取视频的指定时间帧，生成图片文件
     *
     * @param source 源文件
     * @param file   图片文件
     * @param time   截图时间 HH:mm:ss.[SSS]
     * @throws IOException
     * @throws InterruptedException
     */
    public boolean screenShots(String source, String file, String time) throws IOException, InterruptedException {

        List<String> commands = new ArrayList<>();
        commands.add(ffmpeg);
        commands.add("-i");
        commands.add(source);
        commands.add("-ss");
        commands.add(time);
        commands.add("-y");
        commands.add("-q:v");
        commands.add("1");
        commands.add("-frames:v");
        commands.add("1");
        commands.add("-f");
        ;
        commands.add("image2");
        commands.add(file);

        Process process = new ProcessBuilder(commands)
                .start();

//        // 读取进程标准输出
//        new Thread(() -> {
//            try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
//                String line = null;
//                while ((line = bufferedReader.readLine()) != null) {
//                    log.info(line);
//                }
//            } catch (IOException e) {
//            }
//        }).start();

        // 读取进程异常输出
        new Thread(() -> {
            try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getErrorStream()))) {
                String line = null;
                while ((line = bufferedReader.readLine()) != null) {
                    log.error(line);
                }
            } catch (IOException e) {
            }
        }).start();

        return process.waitFor() == 0;
    }
}


